/*******************************************************************************
 * Copyright (c) 2011, 2012 Red Hat, Inc.
 *  All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Red Hat, Inc. - initial API and implementation
 *
 * @author Bob Brodt
 ******************************************************************************/

package org.eclipse.bpmn2.modeler.core.adapters;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;

import org.eclipse.bpmn2.Activity;
import org.eclipse.bpmn2.CallableElement;
import org.eclipse.bpmn2.CatchEvent;
import org.eclipse.bpmn2.ChoreographyActivity;
import org.eclipse.bpmn2.ConversationNode;
import org.eclipse.bpmn2.Event;
import org.eclipse.bpmn2.EventDefinition;
import org.eclipse.bpmn2.ExtensionAttributeValue;
import org.eclipse.bpmn2.FlowElement;
import org.eclipse.bpmn2.FlowElementsContainer;
import org.eclipse.bpmn2.FlowNode;
import org.eclipse.bpmn2.Gateway;
import org.eclipse.bpmn2.InteractionNode;
import org.eclipse.bpmn2.LoopCharacteristics;
import org.eclipse.bpmn2.ThrowEvent;
import org.eclipse.bpmn2.impl.ActivityImpl;
import org.eclipse.bpmn2.impl.CallableElementImpl;
import org.eclipse.bpmn2.impl.CatchEventImpl;
import org.eclipse.bpmn2.impl.ChoreographyActivityImpl;
import org.eclipse.bpmn2.impl.ConversationNodeImpl;
import org.eclipse.bpmn2.impl.EventDefinitionImpl;
import org.eclipse.bpmn2.impl.EventImpl;
import org.eclipse.bpmn2.impl.FlowElementImpl;
import org.eclipse.bpmn2.impl.FlowElementsContainerImpl;
import org.eclipse.bpmn2.impl.FlowNodeImpl;
import org.eclipse.bpmn2.impl.GatewayImpl;
import org.eclipse.bpmn2.impl.InteractionNodeImpl;
import org.eclipse.bpmn2.impl.LoopCharacteristicsImpl;
import org.eclipse.bpmn2.impl.ThrowEventImpl;
import org.eclipse.bpmn2.modeler.core.ToolTipProvider;
import org.eclipse.bpmn2.modeler.core.model.Bpmn2ModelerFactory;
import org.eclipse.bpmn2.modeler.core.runtime.ITargetRuntimeProvider;
import org.eclipse.bpmn2.modeler.core.runtime.PropertyExtensionDescriptor;
import org.eclipse.bpmn2.modeler.core.runtime.TargetRuntime;
import org.eclipse.bpmn2.modeler.core.runtime.TargetRuntimeAdapter;
import org.eclipse.bpmn2.modeler.core.utils.ModelUtil;
import org.eclipse.bpmn2.util.Bpmn2Resource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.edit.provider.ComposeableAdapterFactory;
import org.eclipse.emf.transaction.TransactionalEditingDomain;

/**
 * This class is a light weight replacement for the {@code ItemProviderAdapter}
 * classes generated by EMF for the BPMN2 model. It uses Java generics to
 * specialize the adapter for a given model object. This allows extension
 * plug-ins to provide their own implementations for BPMN2 types.
 * 
 * This can be extended by contributing plug-ins using the
 * {@code <propertyExtension>} extension point. See the
 * {@code org.eclipse.bpmn2.modeler.runtime} extension point documentation for
 * more details.
 */
public class ExtendedPropertiesAdapter<T extends EObject> extends AdapterImpl implements IResourceProvider, ITargetRuntimeProvider {

	/** Property key for the verbose description of this model object */
	public final static String LONG_DESCRIPTION = "long.description"; //$NON-NLS-1$
	/** Property key indicating if this object can be edited, or is read-only */
	public final static String UI_CAN_EDIT = "ui.can.edit"; //$NON-NLS-1$
	/**
	 * Any adapter that uses this must override setValue() which understands
	 * how to convert a String to the required type. This is used in
	 * ComboObjectEditor (maybe others in the future)
	 */
	public final static String UI_CAN_EDIT_INLINE = "ui.can.edit.inline"; //$NON-NLS-1$
	public final static String UI_CAN_CREATE_NEW = "ui.can.create.new"; //$NON-NLS-1$
	/**
	 * For Combo boxes (ComboObjectEditor), this indicates that an empty
	 * selection will be added to the list of possible choices; For Text fields
	 * (TextObjectEditor), this indicates that the actual value of a feature
	 * should be used as the edit field text instead of its textual
	 * representation as returned by @link ModelUtil#getDisplayName(). In this
	 * case, if the value is null, it will be replaced with an empty string.
	 */
	public final static String UI_CAN_SET_NULL = "ui.can.set.null"; //$NON-NLS-1$
	/**
	 * Property key indicating if this object is multi-valued (i.e. requires a
	 * Comob box or similar selection widget)
	 */ 
	public final static String UI_IS_MULTI_CHOICE = "ui.is.multi.choice"; //$NON-NLS-1$
	/**
	 * Property key that defines the ObjectEditor class that should be used for
	 * the given feature instead of the default based on the feature type. A new
	 * instance of this ObjectEditor type is constructed by
	 * {@see AbstractDetailComposite#bindAttribute(Composite,EObject,EAttribute,String)}
	 * and displayed in the Property Sheets to edit the object feature.
	 */
	public final static String UI_OBJECT_EDITOR_CLASS = "ui.object.editor.class"; //$NON-NLS-1$
	/** Property key for the the {@code ObjectDescriptor} object */
	public static final String OBJECT_DESCRIPTOR = "object.descriptor"; //$NON-NLS-1$
	/** Property key for the {@code FeatureDescriptor} object */
	public static final String FEATURE_DESCRIPTOR = "feature.descriptor"; //$NON-NLS-1$
	/** Property key for the line number in XML document where this object is defined */
	public static final String LINE_NUMBER = "line.number"; //$NON-NLS-1$
	/**
	 * Boolean property key to indicate the feature is a BPMN 2.0 extension
	 * defined by a specific Target Runtime
	 */
	public static final String IS_EXTENSION_FEATURE = "is.extension.feature"; //$NON-NLS-1$
	/**
	 * Used by the Data Input/Output Association Property Adapter to select only
	 * valid source/target objects in scope for the Association.
	 */
	public final static String UI_SHOW_ITEMS_IN_SCOPE = "show.items.in.scope"; //$NON-NLS-1$

	protected Hashtable<String, Object> properties = new Hashtable<String, Object>();

	/**
	 * The map of EStructuralFeatures that need {@code FeatureDescriptor}
	 * provider classes.
	 */
	private Hashtable<
		EStructuralFeature, // feature type
		Hashtable<String,Object>> // property key and value
			featureProperties = new Hashtable<EStructuralFeature, Hashtable<String,Object>>();
	
	/**
	 * The Adapter Factory that was used to construct this
	 * {@code ExtendedPropertiesAdapter}
	 */
	private AdapterFactory adapterFactory;
	
	/**
	 * Constructor that adapts the given model object.
	 * 
	 * @param adapterFactory the Adapter Factory instance.
	 * @param object the object to be adapted.
	 */
	public ExtendedPropertiesAdapter(AdapterFactory adapterFactory, T object) {
		this.adapterFactory = adapterFactory;
		setTarget(object);
	}
	
	/**
	 * Convenience method for creating and adapting a model object for an
	 * {@code ExtendedPropertiesAdapter}.
	 * 
	 * @param object the object to be adapted.
	 * @return the {@code ExtendedPropertiesAdapter}
	 */
	@SuppressWarnings("rawtypes")
	public static ExtendedPropertiesAdapter adapt(Object object) {
		if (object instanceof EClass) {
			object = getDummyObject((EClass)object);
		}
		if (object instanceof EObject)
			return adapt((EObject)object);
		return null;
	}

	@SuppressWarnings("rawtypes")
	public static ExtendedPropertiesAdapter adapt(Resource resource, EClass eClass) {
		TargetRuntime rt = getTargetRuntime(resource);
        PropertyExtensionDescriptor ped = rt.getPropertyExtension(eClass.getInstanceClass());
        if (ped==null && rt != TargetRuntime.getDefaultRuntime())
            ped = TargetRuntime.getDefaultRuntime().getPropertyExtension(eClass.getInstanceClass());
        if (ped!=null)
            return ped.getAdapter(new AdapterFactoryImpl(), eClass);
        
		EObject object = getDummyObject(eClass);
		TargetRuntimeAdapter.adapt(object, rt);
		ExtendedPropertiesAdapter adapter = adapt(object);
		adapter.setResource(resource);
		adapter.setTargetRuntime(rt);
		return adapter;
	}
	
	/**
	 * Convenience method for creating and adapting a feature of a model object
	 * for an {@code ExtendedPropertiesAdapter}.
	 * 
	 * @param object the object to be adapted.
	 * @param feature a feature of the given object.
	 * @return the {@code ExtendedPropertiesAdapter}
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static ExtendedPropertiesAdapter adapt(Resource resource, EObject object, EStructuralFeature feature) {
		Assert.isTrue(feature!=null);
		if (object instanceof EClass) {
			object = getDummyObject((EClass)object);
		}
		else if (resource==null)
			resource = getResource(object);
		
		ExtendedPropertiesAdapter adapter = null;
		TargetRuntime rt = null;
		if (resource!=null) {
			rt = TargetRuntimeAdapter.getTargetRuntime(resource);
			if (rt!=null)
				TargetRuntimeAdapter.adapt(object, rt);
		}
		adapter = (ExtendedPropertiesAdapter) AdapterUtil.adapt(object, ExtendedPropertiesAdapter.class);
		
		if (adapter==null)
			adapter = new ExtendedPropertiesAdapter(new AdapterFactoryImpl(), object);

		if (adapter!=null) {
			adapter.setTarget(object);
			adapter.getObjectDescriptor().setObject(object);
			adapter.getFeatureDescriptor(feature).setObject(object);
		}
		
		return adapter;
	}
	
	@Deprecated
	public static ExtendedPropertiesAdapter adapt(EClass object) {
		throw new IllegalArgumentException("ExtendedPropertiesAdapter: Can't adapt "+((EClass)object).getName());
	}
	
	public static ExtendedPropertiesAdapter adapt(EObject object) {
		if (object==null)
			return null;
		
		if (object instanceof EClass) {
			throw new IllegalArgumentException("ExtendedPropertiesAdapter: Can't adapt "+((EClass)object).getName());
		}
		if (object instanceof ExtensionAttributeValue) {
			if (object.eContainer()!=null)
				object = object.eContainer();
		}

		ExtendedPropertiesAdapter adapter = (ExtendedPropertiesAdapter) AdapterUtil.adapt(object, ExtendedPropertiesAdapter.class);
		if (adapter==null)
			adapter = new ExtendedPropertiesAdapter(new AdapterFactoryImpl(), object);
		if (adapter!=null) {
			adapter.setTarget(object);
			adapter.getObjectDescriptor().setObject(object);
		}
		return adapter;
	}

	/**
	 * Dummy objects are constructed when needed for an
	 * {@code ExtendedPropertiesAdapter}. The adapter factory (@see
	 * org.eclipse.bpmn2
	 * .modeler.ui.adapters.Bpmn2EditorItemProviderAdapterFactory) knows how to
	 * construct an ExtendedPropertiesAdapter from an EClass, however the
	 * adapter itself needs an EObject. This method constructs and caches these
	 * dummy objects as they are needed.
	 * 
	 * @param eclass EClass of the object to create.
	 * @return an orphan EObject of the given EClass type.
	 */
	public static EObject getDummyObject(EClass eClass) {
		EObject object = null;
		EFactory factory = eClass.getEPackage().getEFactoryInstance();
		if (factory instanceof Bpmn2ModelerFactory) {
			if (eClass.isAbstract()) {
    	    	// These are abstract types that are supported by {@see Bpmn2ModelerFactory#create(EClass)}
    	    	// Additional abstract types can be added here:
    	    	if (eClass.getInstanceClass()==Activity.class) {
    	    		object = new ActivityImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==CallableElement.class) {
    	    		object = new CallableElementImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==CatchEvent.class) {
    	    		object = new CatchEventImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==ChoreographyActivity.class) {
    	    		object = new ChoreographyActivityImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==ConversationNode.class) {
    	    		object = new ConversationNodeImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==Event.class) {
    	    		object = new EventImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==EventDefinition.class) {
    	    		object = new EventDefinitionImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==FlowElement.class) {
    	    		object = new FlowElementImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==FlowElementsContainer.class) {
    	    		object = new FlowElementsContainerImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==FlowNode.class) {
    	    		object = new FlowNodeImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==Gateway.class) {
    	    		object = new GatewayImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==InteractionNode.class) {
    	    		object = new InteractionNodeImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==LoopCharacteristics.class) {
    	    		object = new LoopCharacteristicsImpl() {};
    	    	}
    	    	else if (eClass.getInstanceClass()==ThrowEvent.class) {
    	    		object = new ThrowEventImpl() {};
    	    	}
    	    	else {
    	    		System.err.println("The abstract BPMN2 type "+eClass.getName()+" can not be constructed");
    	    	}
			}
			else
				object = ((Bpmn2ModelerFactory)factory).createInternal(eClass);
		}
		else
			object = factory.create(eClass);
		return object;
	}

	/**
	 * Returns the Adapter Factory that created this
	 * {@code ExtendedPropertiesAdapter}
	 * 
	 * @return the Adapter Factory.
	 */
	public AdapterFactory getAdapterFactory() {
		return adapterFactory;
	}
	
	/**
	 * Sets the Object Descriptor for this adapter. See {@code ObjectDescriptor}
	 * for details.
	 * 
	 * @param od the ObjectDescriptor instance.
	 */
	public void setObjectDescriptor(ObjectDescriptor<T> od) {
		setProperty(OBJECT_DESCRIPTOR,od);
		od.setOwner(this);
	}

	/**
	 * Returns the Object Descriptor for this adapter. If an Object Descriptor
	 * has not been set, a default implementation is created and set for this
	 * adapter.
	 * 
	 * @return the Object Descriptor instance.
	 */
	@SuppressWarnings("unchecked")
	public ObjectDescriptor<T> getObjectDescriptor() {
		ObjectDescriptor<T> od = (ObjectDescriptor<T>) getProperty(OBJECT_DESCRIPTOR);
		if (od==null) {
			setObjectDescriptor(od = new ObjectDescriptor<T>(this, (T)getTarget()));
		}
		return od;
	}

	/**
	 * Check if a FeatureDescriptor exists for the given feature.
	 * 
	 * @param feature an EStructuralFeature
	 * @return true if the adapter has a FeatureDescriptor, false if not.
	 */
	@SuppressWarnings("unchecked")
	public boolean hasFeatureDescriptor(EStructuralFeature feature) {
		FeatureDescriptor<T> fd = (FeatureDescriptor<T>) getProperty(feature,FEATURE_DESCRIPTOR);
		return fd!=null;
	}

	/**
	 * Returns the Feature Descriptor for this adapter. If a Feature Descriptor has not been
	 * set, a default implementation is created and set for this adapter.
	 * 
	 * @param feature an EStructuralFeature
	 * @return the FeatureDescriptor instance.
	 */
	@SuppressWarnings("unchecked")
	public FeatureDescriptor<T> getFeatureDescriptor(EStructuralFeature feature) {
		FeatureDescriptor<T> fd = (FeatureDescriptor<T>) getProperty(feature,FEATURE_DESCRIPTOR);
		if (fd==null) {
			setFeatureDescriptor(feature, fd = new FeatureDescriptor<T>(this, (T)getTarget(), feature));
		}
		return fd;
	}
	
	/**
	 * Convenience method for getting the Feature Descriptor by feature name.
	 * 
	 * @param featureName name of a feature.
	 * @return same as {@code getFeatureDescriptor(EStructuralFeature)}
	 */
	public FeatureDescriptor<T> getFeatureDescriptor(String featureName) {
		EStructuralFeature feature = getFeature(featureName);
		return getFeatureDescriptor(feature);
	}
	
	/**
	 * Sets the Feature Descriptor for the given feature. Clients may use this to override
	 * default behavior for specific object features.
	 * 
	 * @param feature an EStructuralFeature
	 * @param fd the Feature Descriptor instance
	 */
	public void setFeatureDescriptor(EStructuralFeature feature, FeatureDescriptor<T> fd) {
		Hashtable<String,Object> props = featureProperties.get(feature);
		if (props==null) {
			props = new Hashtable<String,Object>();
			featureProperties.put(feature,props);
		}
		fd.setOwner(this);
		props.put(FEATURE_DESCRIPTOR, fd);
	}

	/**
	 * Lookup method for the given feature name.
	 * 
	 * @param name name of a feature
	 * @return the EStructuralFeature of the object provided by the Object Descriptor.
	 */
	public EStructuralFeature getFeature(String name) {
		EObject object = getObjectDescriptor().object;
		if (object instanceof ExtensionAttributeValue) {
			EObject container = ((ExtensionAttributeValue)object).eContainer();
			if (container!=null) {
				ExtendedPropertiesAdapter adapter = this.adapt(container);
				if (adapter!=null)
					return adapter.getFeature(name);
			}
		}
		for (Entry<EStructuralFeature, Hashtable<String, Object>> entry : featureProperties.entrySet()) {
			EStructuralFeature feature = entry.getKey();
			if (feature.getName().equals(name)) {
				return feature;
			}
		}
		return null;
	}

	/**
	 * Returns a list of all features that have Feature Descriptors in this adapter.
	 * 
	 * @return a list of EStructuralFeatures
	 */
	public List<EStructuralFeature> getFeatures() {
		EObject object = getObjectDescriptor().object;
		if (object instanceof ExtensionAttributeValue) {
			EObject container = ((ExtensionAttributeValue)object).eContainer();
			if (container!=null) {
				ExtendedPropertiesAdapter adapter = this.adapt(container);
				if (adapter!=null)
					return adapter.getFeatures();
			}
		}
		List<EStructuralFeature> features = new ArrayList<EStructuralFeature>();
		features.addAll(featureProperties.keySet());
		return features;
	}
	
	public List<EStructuralFeature> getExtensionFeatures() {
		List<EStructuralFeature> features = new ArrayList<EStructuralFeature>();
		for (EStructuralFeature f : getFeatures()) {
			FeatureDescriptor fd = getFeatureDescriptor(f);
			if (fd.getProperty(IS_EXTENSION_FEATURE)!=null)
				features.add(f);
		}
		return features;
	}
	
	/**
	 * Return a list of all Object Properties.
	 * 
	 * @return a list of Object Properties
	 */
	private Hashtable <String, Object> getObjectProperties() {
		if (properties==null)
			properties = new Hashtable <String,Object>();
		return properties;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.bpmn2.modeler.core.adapters.ObjectPropertyProvider#getProperty(java.lang.String)
	 */
	public Object getProperty(String key) {
		return getObjectProperties().get(key);
	}

	@SuppressWarnings("unchecked")
	public <T extends Object> T getProperty(Class<T> clazz) {
		return (T) getProperty(clazz.getName());
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.bpmn2.modeler.core.adapters.ObjectPropertyProvider#setProperty(java.lang.String, java.lang.Object)
	 * 
	 * Set the value for an Object Property.
	 * 
	 * @param key property name.
	 * @param value property value.
	 */
	public void setProperty(String key, Object value) {
		if (value==null)
			getObjectProperties().remove(key);
		else
			getObjectProperties().put(key, value);
	}

	public <T extends Object> void setProperty(Class<T> clazz, Object value) {
		setProperty(clazz.getName(), value);
	}

	/**
	 * Gets the value of the Feature Property.
	 *
	 * @param feature the feature
	 * @param key the key
	 * @return the property value
	 */
	public Object getProperty(EStructuralFeature feature, String key) {
		Hashtable<String,Object> props = featureProperties.get(feature);
		if (props==null) {
			props = new Hashtable<String,Object>();
			featureProperties.put(feature,props);
		}
		return props.get(key);
	}

	/**
	 * Convenience method to get the boolean value of an Object Property.
	 * 
	 * @param key the Object Property key
	 * @return true or false depending on the Object Property value. If this is
	 *         not a boolean property, return false.
	 */
	public boolean getBooleanProperty(String key) {
		Object result = getProperty(key);
		if (result instanceof Boolean)
			return ((Boolean)result);
		return false;
	}

	/**
	 * Convenience method to get the boolean value of a Feature Property.
	 * 
	 * @param feature the object's feature
	 * @param key the Feature Property key
	 * @return true or false depending on the Object Property value. If this is
	 *         not a boolean property, return false.
	 */
	public boolean getBooleanProperty(EStructuralFeature feature, String key) {
		Object result = getProperty(feature, key);
		if (result instanceof Boolean)
			return ((Boolean)result);
		return false;
	}

	/**
	 * Set the value of a Feature Property.
	 * 
	 * @param feature the object's feature
	 * @param key the Feature Property key
	 * @param value the property value
	 */
	public void setProperty(EStructuralFeature feature, String key, Object value) {
		Hashtable<String,Object> props = featureProperties.get(feature);
		if (props==null) {
			props = new Hashtable<String,Object>();
			featureProperties.put(feature,props);
		}
		props.put(key, value);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.notify.impl.AdapterImpl#setTarget(org.eclipse.emf.common.notify.Notifier)
	 */
	public void setTarget(Notifier newTarget) {
		super.setTarget(newTarget);
		if (newTarget instanceof EObject && !(newTarget instanceof EClass)) {
			EObject object = (EObject)newTarget;
			for (Adapter a : object.eAdapters()) {
				if (a instanceof ExtendedPropertiesAdapter)
					return;
			}
			object.eAdapters().add(this);
			setResource(getResource(object));
		}
	}

	public String getDescription(EObject object) {
		return ToolTipProvider.INSTANCE.getLongDescription(adapterFactory,object);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.bpmn2.modeler.core.adapters.ObjectPropertyProvider#getResource()
	 */
	@Override
	public Resource getResource() {
		Resource resource = getProperty(Resource.class);
		if (resource==null) {
			ObjectDescriptor<T> od = (ObjectDescriptor<T>) getProperty(OBJECT_DESCRIPTOR);
			if (od!=null) {
				IResourceProvider rp = AdapterRegistry.INSTANCE.adapt(od.object.eContainer(), IResourceProvider.class);
				if (rp!=null && rp!=this) {
					resource = rp.getResource();
					setResource(resource);
				}
			}
		}
		return resource;
	}
	
	@Override
	public void setResource(Resource resource) {
		setProperty(Resource.class, resource);
	}

	@Override
	public TargetRuntime getTargetRuntime() {
		TargetRuntime rt = getProperty(TargetRuntime.class);
		if (rt==null) {
			Resource resource = getResource();
			rt = getTargetRuntime(resource);
			if (rt!=null) {
				setTargetRuntime(rt);
			}
		}
		return rt;
	}
	
	@Override
	public void setTargetRuntime(TargetRuntime rt) {
		setProperty(TargetRuntime.class, rt);
	}

	/**
	 * Given an EObject always returns the BPMN2 Resource that is associated
	 * with that object. This may involve searching for all Resources in the
	 * ResourceSet that the EObject belongs to. This also searches for a
	 * Resource in the object's {@link InsertionAdapter} if the object is not
	 * yet contained in any Resource.
	 * 
	 * @param object
	 * @return
	 */
	public static Resource getResource(Notifier object) {
		if (object==null)
			return null;
		
		Resource resource = null;
		EObject eObject = null;
		
		if (object instanceof EObject) {
			eObject = (EObject) object;
			resource = ((EObject)object).eResource();
		}
		
		if (object instanceof Resource) {
			resource = (Resource) object;
		}

		if (object instanceof ResourceSet) {
			ResourceSet rs = (ResourceSet) object;
			if (rs.getResources().size()>0)
				resource = rs.getResources().get(0);
		}
		
		if (resource==null) {
			IResourceProvider rp = AdapterRegistry.INSTANCE.adapt(object, IResourceProvider.class);
			if (rp!=null)
				resource = rp.getResource();
		}
		
		if (resource==null && eObject!=null) {
			for (Adapter a : eObject.eAdapters()) {
				if (a instanceof IResourceProvider) {
					resource = ((IResourceProvider)a).getResource();
					if (resource!=null)
						break;
				}
			}
		}
			
		// make sure we get a BPMN2 Resource
		if (resource!=null && !(resource instanceof Bpmn2Resource)) {
			ResourceSet rs = resource.getResourceSet();
			if (rs!=null) {
				for (Resource r : rs.getResources()) {
					if (r instanceof Bpmn2Resource) {
						return r;
					}
				}
			}
			return null;
		}
		
		return resource;
	}
	
	public static TargetRuntime getTargetRuntime(Notifier object) {
		if (object==null)
			return null;
		
		TargetRuntime rt = null;
		
		if (object instanceof ResourceSet) {
			Resource resource = getResource(object);
			if (resource!=null)
				object = resource;
		}
		
		for (Adapter a : object.eAdapters()) {
			if (a instanceof ITargetRuntimeProvider) {
				rt = ((ITargetRuntimeProvider) a).getTargetRuntime();
				if (rt!=null)
					return rt;
			}
		}
		
		// last chance: can we find the resource this object belongs to?
		if (!(object instanceof Resource)) {
			Resource resource = getResource(object);
			if (resource!=null) {
				for (Adapter a : resource.eAdapters()) {
					if (a instanceof ITargetRuntimeProvider) {
						rt = ((ITargetRuntimeProvider)a).getTargetRuntime();
						if (rt!=null)
							return rt;
					}
				}
			}
		}
			
		
		return null;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.bpmn2.modeler.core.adapters.ObjectPropertyProvider#getEditingDomain()
	 */
	public TransactionalEditingDomain getEditingDomain() {
		EditingDomain result = null;
		if (adapterFactory instanceof IEditingDomainProvider) {
			result = ((IEditingDomainProvider) adapterFactory).getEditingDomain();
		}
		if (result == null) {
			if (adapterFactory instanceof ComposeableAdapterFactory) {
				AdapterFactory rootAdapterFactory = ((ComposeableAdapterFactory) adapterFactory)
						.getRootAdapterFactory();
				if (rootAdapterFactory instanceof IEditingDomainProvider) {
					result = ((IEditingDomainProvider) rootAdapterFactory).getEditingDomain();
				}
			}
		}
		// it's gotta be a Transactional Editing Domain or nothing!
		if (result instanceof TransactionalEditingDomain)
			return (TransactionalEditingDomain)result;
		return null;
	}

	/**
	 * Compare two EObjects. The default implementation of this method compares the values of
	 * identical features of both objects. This method recognizes features that are {@code StringWrapper}
	 * (proxy) objects and compares their string values.
	 * 
	 * @param thisObject an EObject
	 * @param otherObject an EObject to be compared against thisObject.
	 * @param similar if true, then the ID attributes of the objects being compared <b>may</b> be different.
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static boolean compare(EObject thisObject, EObject otherObject, boolean similar) {
		for (EStructuralFeature f : thisObject.eClass().getEAllStructuralFeatures()) {
			// IDs are allowed to be different
			if (similar && "id".equals(f.getName())) //$NON-NLS-1$
				continue;
			if (otherObject.eClass().getEStructuralFeature(f.getName())==null) {
				if (similar)
					continue;
				return false;
			}
				
			Object v1 = otherObject.eGet(f);
			Object v2 = thisObject.eGet(f);
			// both null? equal!
			if (v1==null && v2==null)
				continue;
			// one or the other null? not equal!
			if (v1==null || v2==null)
				return false;
			// both not null? do a default compare...
			if (!v1.equals(v2)) {
				// the default Object.equals(obj) fails:
				// for Dynamic EObjects (used here as "proxies") only compare their proxy URIs 
				if (ModelUtil.isStringWrapper(v1) && ModelUtil.isStringWrapper(v2)) {
					v1 = ModelUtil.getStringWrapperValue(v1);
					v2 = ModelUtil.getStringWrapperValue(v2);
					if (v1==null && v2==null)
						continue;
					if (v1==null || v2==null)
						return false;
					if (v1.equals(v2))
						continue;
				}
				else if (v1 instanceof EObject && v2 instanceof EObject) {
					// for all other EObjects, do a deep compare...
					ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt((EObject)v1);
					if (adapter!=null) {
						if (adapter.getObjectDescriptor().compare((EObject)v2,similar))
							continue;
					}
				}
				return false;
			}
		}
		return true;
	}
}
